#include "TcpClientInstance.h"
#include "TcpClient.h"
#include "assert.h"
#include "data_record.h"
#include <thread>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <mutex>
#include <queue>
#include <arpa/inet.h>

#include <fstream>
#include <sstream>

using namespace std;

/* check frame head if 0000 0001 then h265 I frame*/
// #define CODEC_PROBE_H265_I_FRAME(data) (0x0 == ((char *)data)[0] && 0x0 == ((char *)data)[1] && 0x0 == ((char *)data)[2] && 0x1 == ((char *)data)[3] && 0x40 ==((char *)data)[4])

// std::ofstream g_test_file;


//以共享指针方式放入，mqtt，socket线程各自维护一个;各自做补发功能
TcpClientInstance::TcpClientInstance()
{

}

TcpClientInstance ::~TcpClientInstance()
{
   
}

TcpClientInstance &TcpClientInstance::getInstance()
{
    static TcpClientInstance instance;
    return instance;
}

/*********************************/
void TcpClientInstance::sendOneFrame(const uint8_t &cameraId, const EncodeSrv::EncodeH265 &enc)
{
    uint32_t tmp32 = 0;
    uint64_t tmp64 = 0;
    
    //command + length
    int headLen = sizeof(uint8_t) + sizeof(uint32_t);
    uint8_t headData[headLen];
    memset(headData, 0, headLen);

    headData[0] = 0b10;
    uint32_t ValueLen = sizeof(uint8_t) * 2 + sizeof(uint64_t) * 2 + enc.frame_data().size();//vaule字段长度的值
    tmp32 = htonl(ValueLen);
    memcpy(headData + sizeof(uint8_t), &tmp32, sizeof(uint32_t));
    m_SendLenVedio += tcpClient->SendData((void *)headData, headLen);
    m_DataLenVedio += headLen;
    // g_test_file.write((char *)headData, headLen);

    //value: cameraId+ 编码帧率+ 帧序号+ 帧时间戳（us）
    int camInfoLen = sizeof(uint8_t) * 2 + sizeof(uint64_t) * 2;
    uint8_t camInfoData[camInfoLen];
    memset(camInfoData, 0, camInfoLen);

    camInfoData[0] = cameraId;    
    camInfoData[1] = enc.frame_ratenum();
    uint64_t frameId = enc.frame_id();
    tmp64 = htobe64(frameId);    
    memcpy(camInfoData + 2, &tmp64, sizeof(uint64_t));  

    uint64_t timestamp = enc.timestamp();
    tmp64 = htobe64(timestamp);
    memcpy(camInfoData + 10, &tmp64, sizeof(uint64_t));    
    m_SendLenVedio += tcpClient->SendData((void *)camInfoData, camInfoLen);
    m_DataLenVedio += camInfoLen;
    // g_test_file.write((char *)camInfoData, camInfoLen);

    //帧数据
    m_SendLenVedio += tcpClient->SendData((void *)enc.frame_data().c_str(), enc.frame_data().size());
    m_DataLenVedio += enc.frame_data().size();

    m_stacFrame[cameraId][frameId] = enc.frame_data().size();
    
    // g_test_file.write((char *)enc.frame_data().c_str(), enc.frame_data().size());

    // Log_Info("zjs_tmp_1224; cameraId:{} frameTime:{}", cameraId, timestamp);

}

void TcpClientInstance::pareOneFrame(const ProtoData &element)
{
    string &topicName = *element.topic.get();
    string &topicData = *element.data.get();

    EncodeSrv::EncodeH265 enc;
    if (!enc.ParsePartialFromString(topicData))
    {
        Log_ERROR("EncodeSrv::EncodeH265 ParsePartialFromString error, topic: {}, end", topicName);
        return;
    }
    if (enc.frame_data().size() == 0)
    {
        Log_ERROR("h265Data size is 0");
        return;
    }
    if (enc.encode_width() != 1920 || enc.encode_height() != 1080)
    {
    	m_wrongRes ++;
	    if (m_wrongRes % 50 == 1)
	    {
	        Log_ERROR("h265Data is not 1920*1080");
	    }
        return;
    }

    if (ENCODE_SRV_H265_CAM0 == topicName)
    {
        sendOneFrame(0b100000, enc);
    }
    else if (ENCODE_SRV_H265_CAM1 == topicName)
    {
        sendOneFrame(0b10000, enc);
    }
    else if (ENCODE_SRV_H265_CAM3 == topicName)
    {
        sendOneFrame(0b1000, enc);
    }
    else if (ENCODE_SRV_H265_CAM2 == topicName)
    {
        sendOneFrame(0b100, enc);
    }
    else if (ENCODE_SRV_H265_CAM10 == topicName)
    {
        sendOneFrame(0b10, enc);
    }
    else if (ENCODE_SRV_H265_CAM8 == topicName)
    {
        sendOneFrame(0b1, enc);
    }
}

//发开始时，附上前面的视频数据
void TcpClientInstance::sendStart(const std::map<std::string, std::vector<ProtoData>> &pieceData, const TriggerEventInfo &trigger)
{
    if (tcpClient == nullptr)
    {
        tcpClient =  std::make_unique<CTcpClient>();

        // m_sockeFile = "/ota/shaotaSocket_" + to_string(trigger.taskId) + ".txt";
        // g_test_file.open(m_sockeFile.c_str());
        // bool ret = g_test_file.is_open();
        // Log_Info("zjs_tmp_shaota;open file:{}  ret:{}", m_sockeFile, ret);        
    }

	m_wrongRes = 0;
    m_DataLenVedio = 0;
    m_SendLenVedio = 0;
    m_stacFrame.clear();    

    //连接成功发送数据
    int typeTimes = 5;
    while (typeTimes)
    {
        if (!tcpClient->ConnectToServer(global_data::g_actsen_host, global_data::g_actsen_port))
        {
            typeTimes--;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            Log_Info("Socket connect failed ,try time: {}", typeTimes);
            continue;
        }
        typeTimes = 0;        
    }    
    
    uint32_t tmp32 = 0;
    uint64_t tmp64 = 0;

    //发送指令    
    int ValueLen = sizeof(uint64_t)*2 + sizeof(uint32_t)*2;
    int TolLen = sizeof(uint8_t) + sizeof(uint32_t)+ ValueLen;
    uint8_t cmd = 0b1;
    uint32_t length = ValueLen;

    uint8_t value[ValueLen];
    tmp64 = htobe64(trigger.recordTime);
    memcpy(value, &tmp64, sizeof(uint64_t));
    tmp64 = htobe64(trigger.taskId);
    memcpy(value + sizeof(uint64_t), &tmp64, sizeof(uint64_t));
    uint32_t width = 1920;
    tmp32 = htonl(width);
    memcpy(value + sizeof(uint64_t) + sizeof(uint64_t), &tmp32, sizeof(uint32_t));
    uint32_t height = 1080;
    tmp32 = htonl(height);
    memcpy(value + sizeof(uint64_t) + sizeof(uint64_t)+ sizeof(uint32_t), &tmp32, sizeof(uint32_t));

    uint8_t finalData[TolLen];
    memset(finalData, 0, TolLen);
    memcpy(finalData, &cmd, sizeof(uint8_t));
    tmp32 = htonl(length);
    memcpy(finalData + sizeof(uint8_t), &tmp32, sizeof(uint32_t));
    memcpy(finalData + sizeof(uint8_t) + sizeof(uint32_t), value, ValueLen);

    long int sendLen = tcpClient->SendData((void *)finalData, TolLen);
    Log_Info("[tcp_statistic] send start; taskId:{} dataLen:{} sendLen:{}", trigger.taskId, TolLen, sendLen);
    // g_test_file.write((char *)finalData, TolLen);

    //发送视频
    auto itMap  = pieceData.find(DAT_FILE_TYPE_VIDEO);
    if (itMap != pieceData.end())
    {
        const std::vector<ProtoData>& vectorTop =  itMap->second;
        for (const auto &element : vectorTop)
        {
            pareOneFrame(element);
        }
    }
}

void TcpClientInstance::send3DVedioStream(const std::string &fileName3D)
{
    FILE *pvFd = NULL;
    uint64_t nLen = 0;
    pvFd = fopen(fileName3D.c_str(), "rb");
    if (pvFd != NULL)
    {
        fseek(pvFd, 0, SEEK_END);
        nLen = ftell(pvFd);

        uint32_t tmp32 = 0;
        //uint64_t tmp64 = 0;
        //command + length
        int headLen = sizeof(uint8_t) + sizeof(uint32_t);
        uint8_t headData[headLen];
        memset(headData, 0, headLen);

        headData[0] = 0b10;
        uint32_t ValueLen = sizeof(uint8_t) + nLen; //vaule字段长度的值
        tmp32 = htonl(ValueLen);
        memcpy(headData+1, &tmp32, sizeof(uint32_t));
        tcpClient->SendData((void *)headData, headLen);
        // g_test_file.write((char *)headData, headLen);

        //value: cameraId
        uint8_t camInfoData = 0b1000000;
        tcpClient->SendData((void *)&camInfoData, sizeof(uint8_t));
        // g_test_file.write((char *)&camInfoData, sizeof(uint8_t));

        //帧数据
        char * FileStream3d = new char[KCpMaxSize];

        uint64_t readLen = 0;
        uint64_t sendLen = 0;
        fseek(pvFd, 0, SEEK_SET);
        while ((readLen = fread(FileStream3d, sizeof(int8_t), KCpMaxSize, pvFd)))
        {
            sendLen += tcpClient->SendData((void *)FileStream3d, readLen);
            // g_test_file.write((char *)FileStream3d, readLen);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));//100
        }
        Log_Info("[tcp_statistic]send 3d data, sendLen:{} fileLen:{}", sendLen, nLen);

        fclose(pvFd);
        delete [] FileStream3d;

        auto ret = remove(fileName3D.c_str());
        Log_Info("remove 3dvedio ret:{}", ret);
    }
}

//发结束时，附上3d视频的文件流
void TcpClientInstance::sendEnd(const TriggerEventInfo &trigger, const std::string &fileName_3d, uint64_t endFrameUtcTime)
{
    //发送3D视频
    send3DVedioStream(fileName_3d);
    
    uint32_t tmp32 = 0;
    uint64_t tmp64 = 0;

    //发送指令 ，计算即将发送的数据包长度：数据区有两个 uint64_t 字段（共16字节），包头有命令字节和长度字段（共5字节）。
    int ValueLen = sizeof(uint64_t)*2;
    int TolLen = sizeof(uint8_t) + sizeof(uint32_t)+ ValueLen;

    //设置命令类型（0b100，表示结束指令），并设置数据区长度。
    uint8_t cmd = 0b100;
    uint32_t length = ValueLen;

    //构造数据区内容：前8字节为结束时的 UTC 时间，后8字节为任务ID，均做字节序转换（大端格式）。
    uint8_t value[ValueLen];
    tmp64 = htobe64(endFrameUtcTime);//结束时带上此时的utc时间
    memcpy(value, &tmp64, sizeof(uint64_t));
    tmp64 = htobe64(trigger.taskId);
    memcpy(value+8, &tmp64, sizeof(uint64_t));

    /*
        这段代码将命令、长度和数据区内容依次拼接到 finalData，形成完整的 TCP 发送数据包，结构如下：
        第1字节：命令类型
        第2~5字节：数据区长度
        第6~21字节：数据区内容（结束时间+任务ID）

    */
    //构造完整的数据包：第1字节为命令类型，接着4字节为数据区长度，后面是数据区内容（结束时间和任务ID）。
    uint8_t finalData[TolLen];
    memset(finalData, 0, TolLen);
    memcpy(finalData, &cmd, sizeof(uint8_t));
    tmp32 = htonl(length);
    memcpy(finalData + sizeof(uint8_t), &tmp32, sizeof(uint32_t));
    memcpy(finalData + sizeof(uint8_t) + sizeof(uint32_t), value, ValueLen);

    //打印统计信息，显示已发送视频数据的总长度（m_DataLenVedio为数据总量，m_SendLenVedio为实际发送量）。
    Log_Info("[tcp_statistic] send vedioTotal; dataLen:{} sendLen:{}", m_DataLenVedio, m_SendLenVedio);

    //通过 TCP 发送结束指令数据包，并打印实际发送长度。
    long int sendLen = tcpClient->SendData((void *)finalData, TolLen);
    Log_Info("[tcp_statistic] send end; dataLen:{} sendLen:{}", TolLen, sendLen);

    // g_test_file.write((char *)finalData, TolLen);

    //打印统计的每帧大小
    // 遍历外层 map 的所有键值对
    //遍历并打印每个摄像头的帧统计信息，包括帧ID和帧大小，以及分辨率异常帧数量（m_wrongRes）。
    string camLog;
    for (const auto& outerPair : m_stacFrame) 
    {
        uint8_t outerKey = outerPair.first;
        camLog = camLog + " camid:" + to_string(outerKey) + ":";
        const map<int64_t, uint32_t>& innerMap = outerPair.second;

        // 再次遍历内层 map 的所有键值对
        for (const auto& innerPair : innerMap) 
        {
            int64_t innerKey = innerPair.first;
            uint32_t value = innerPair.second;
            camLog = camLog + "{" + to_string(innerKey) + "," + to_string(value) + "}";
        }
    }
    Log_Info("[tcp_statistic] frame stat;{}", camLog);
	Log_Info("[tcp_statistic] wrong resolution frames:{}", m_wrongRes);

    // delete tcpClient;
    // tcpClient = nullptr;

    // g_test_file.close();

}

